package org.mybatis.sharding.ibatis;

import java.sql.SQLException;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mybatis.sharding.TableShard;
import org.mybatis.sharding.config.exception.ShardingPropertyNotFoundException;
import org.mybatis.sharding.excution.ExitOperationsCollector;
import org.mybatis.sharding.excution.TableExcutionStrategy;
import org.mybatis.sharding.excution.colletor.ListSortExitOperationCollector;
import org.mybatis.sharding.excution.exit.ConcatListExitStrategy;
import org.mybatis.sharding.excution.exit.ConcatMapExitStrategy;
import org.mybatis.sharding.excution.exit.ExitStrategy;
import org.mybatis.sharding.excution.exit.FirstNotNullExitStrategy;
import org.mybatis.sharding.excution.exit.SumExitStrategy;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapExecutor;

/**
 * 
 * @author <a href="mailto:kerrigan@alibaba-inc.com">Argan Wang</a>
 *
 */
public  class ShardedSqlMapClientTemplate implements Operations {
    protected final Log         logger = LogFactory.getLog(getClass());

    private TableExcutionStrategy tableExcutionStrategy;
    private List<TableShard>         shards;
    private SqlMapClient        sqlMapClient;

    public ShardedSqlMapClientTemplate(TableExcutionStrategy tableExcutionStrategy, List<TableShard> shards,
            SqlMapClient sqlMapClient) {
        this.shards = shards;
        this.tableExcutionStrategy = tableExcutionStrategy;
        this.sqlMapClient = sqlMapClient;
    }

    public final Object execute(ShardOperationCallback callback,Object params, ExitStrategy exitStrategy) throws ShardingPropertyNotFoundException {
        return this.execute(callback, params,"execute()", exitStrategy, null);
    }

    public final Object execute(ShardOperationCallback callback,Object params, String opName, ExitStrategy exitStrategy) throws ShardingPropertyNotFoundException {
        return this.execute(callback, params, opName, exitStrategy, null);
    }

    public final Object execute(ShardOperationCallback callback, Object params, String opName, ExitStrategy exitStrategy,
            ExitOperationsCollector collector) throws ShardingPropertyNotFoundException {
        return this.tableExcutionStrategy.apply(this.shards, new SqlMapClientCallbackOperation(callback, params,this.sqlMapClient,
                opName), exitStrategy, collector);
    }

    public int delete(final String statementName, final Object parameterObject) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        Integer result = (Integer) this.execute(callback,parameterObject, new SumExitStrategy());
        return result == null ? 0 : result.intValue();
    }

    public Object insert(final String statementName, final Object parameterObject) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return this.execute(callback,parameterObject, "insert(" + statementName + ")", new FirstNotNullExitStrategy());
    }

    @SuppressWarnings("unchecked")
    public List queryForList(final String statementName, final Object parameterObject) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return (List) this.execute(callback,parameterObject, "queryForList(" + statementName + ")", new ConcatListExitStrategy());
    }

    @SuppressWarnings("unchecked")
    public List queryForListSorted(final String statementName, final Object parameterObject, Comparator comparator) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return (List) this.execute(callback,parameterObject, "queryForListSorted(" + statementName + ")", new ConcatListExitStrategy(),
                new ListSortExitOperationCollector(comparator));
    }

    @SuppressWarnings("unchecked")
    public List queryForList(final String statementName) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };
        return (List) this.execute(callback, null,"queryForList(" + statementName + ")", new ConcatListExitStrategy());
    }

    @SuppressWarnings("unchecked")
    public List queryForListSorted(final String statementName, Comparator comparator) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return (List) this.execute(callback, null,"queryForListSorted(" + statementName + ")", new ConcatListExitStrategy(),
                new ListSortExitOperationCollector(comparator));
    }

    @SuppressWarnings("unchecked")
    public Map queryForMap(final String statementName, final Object parameterObject, final String keyProperty) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return (Map) this.execute(callback, "queryForMap(" + statementName + ")", new ConcatMapExitStrategy());
    }

    @SuppressWarnings("unchecked")
    public Map queryForMap(final String statementName, final Object parameterObject, final String keyProperty,
            final String valueProperty) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };
        return (Map) this.execute(callback,parameterObject, "queryForMap(" + statementName + ")", new ConcatMapExitStrategy());
    }

    public Object queryForObject(final String statementName, final Object parameterObject) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return this.execute(callback, parameterObject,"queryForObject(" + statementName + ")", new FirstNotNullExitStrategy());
    }

    public Object queryForObject(final String statementName, final Object parameterObject, final Object resultObject) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return this.execute(callback,parameterObject, "queryForObject(" + statementName + ")", new FirstNotNullExitStrategy());
    }

    public int update(final String statementName, final Object parameterObject) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        Integer result = (Integer) this.execute(callback, parameterObject,"update(" + statementName + ")", new SumExitStrategy());
        return result;
    }

    public int queryForCount(final String statementName, final Object parameterObject) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return (Integer) this.execute(callback, parameterObject,"queryForObject(" + statementName + ")", new SumExitStrategy());
    }

    @SuppressWarnings("unchecked")
    public List queryForList(final String statementName, final Object parameterObject,
            ExitOperationsCollector exitOperationsCollector) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return (List) this.execute(callback,parameterObject, "queryForList(" + statementName + ")", new ConcatListExitStrategy(),
                exitOperationsCollector);
    }

    @SuppressWarnings("unchecked")
    public List queryForList(final String statementName, ExitOperationsCollector exitOperationsCollector) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };
        return (List) this.execute(callback, null,"queryForList(" + statementName + ")", new ConcatListExitStrategy(),
                exitOperationsCollector);
    }

    @SuppressWarnings("unchecked")
    public List queryForListSorted(final String statementName, final Object parameterObject) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return (List) this.execute(callback,parameterObject, "queryForListSorted(" + statementName + ")", new ConcatListExitStrategy(),
                new ListSortExitOperationCollector());

    }

    @SuppressWarnings("unchecked")
    public List queryForListSorted(final String statementName) throws ShardingPropertyNotFoundException {
    	ShardOperationCallback callback = new ShardOperationCallback() {

            public Object doInShardOperation(SqlMapExecutor executor ,Object params) throws SQLException {
                return executor.delete(statementName, params);
            }

        };

        return (List) this.execute(callback, null,"queryForListSorted(" + statementName + ")", new ConcatListExitStrategy(),
                new ListSortExitOperationCollector());
    }

}
